package cn.edu.sjtu.stap.sdg.builder;

import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Queue;
import java.util.Set;

import org.aspectj.ajdt.internal.compiler.ast.AdviceDeclaration;
import org.aspectj.ajdt.internal.compiler.ast.Proceed;
import org.aspectj.org.eclipse.jdt.internal.compiler.ast.ASTNode;
import org.aspectj.org.eclipse.jdt.internal.compiler.ast.AbstractMethodDeclaration;
import org.aspectj.org.eclipse.jdt.internal.compiler.ast.AllocationExpression;
import org.aspectj.org.eclipse.jdt.internal.compiler.ast.ExplicitConstructorCall;
import org.aspectj.org.eclipse.jdt.internal.compiler.ast.MessageSend;
import org.aspectj.org.eclipse.jdt.internal.compiler.lookup.InvocationSite;
import org.aspectj.org.eclipse.jdt.internal.compiler.lookup.MethodBinding;

import cn.edu.sjtu.stap.cfg.CfgCall;
import cn.edu.sjtu.stap.cfg.CfgNodeKind;
import cn.edu.sjtu.stap.cfg.ControlFlowGraph;
import cn.edu.sjtu.stap.cg.CallGraph;
import cn.edu.sjtu.stap.cg.CallSiteNotFoundException;
import cn.edu.sjtu.stap.cg.CgAdvice;
import cn.edu.sjtu.stap.cg.CgMethod;
import cn.edu.sjtu.stap.cg.MethodNotFoundException;
import cn.edu.sjtu.stap.graph.GraphNode;
import cn.edu.sjtu.stap.sdg.*;

public class CompleteSdgBuilder {
    private Map<AbstractMethodDeclaration, SystemDependenceGraph> Sdgs = new HashMap<AbstractMethodDeclaration, SystemDependenceGraph>();

    private SystemDependenceGraph currentSdg;

    /**
     * 
     * @param cg
     *            For connecting the call site and advices
     * @param cfgs:
     *            Intra-cfg of every method
     * @param main:
     *            for facilitizing the graph arrangemetn, constructe the graph
     *            from the Main Method as described in the Paper
     * 
     */
    public void buildSdg(CallGraph cg,
            Map<AbstractMethodDeclaration, ControlFlowGraph> cfgs,
            Map<AbstractMethodDeclaration, SystemDependenceGraph> sdgs,
            AbstractMethodDeclaration main) {
        currentSdg = internalBuild(cg, cfgs, sdgs, main);
        Sdgs.put(main, currentSdg);
    }

    private SystemDependenceGraph internalBuild(CallGraph cg,
            Map<AbstractMethodDeclaration, ControlFlowGraph> cfgs,
            Map<AbstractMethodDeclaration, SystemDependenceGraph> sdgs,
            AbstractMethodDeclaration main) {
        SystemDependenceGraph mainSdg = sdgs.get(main);
        if (null == mainSdg) {
            throw new IllegalArgumentException("Main() Entry not found!");
        }
        SystemDependenceGraph result = new SystemDependenceGraph(mainSdg);

        Queue<AbstractMethodDeclaration> methodsToProcess = new LinkedList<AbstractMethodDeclaration>();
        Set<AbstractMethodDeclaration> processed = new HashSet<AbstractMethodDeclaration>();
        methodsToProcess.add(main);

        while (!methodsToProcess.isEmpty()) {
            AbstractMethodDeclaration callerMethod = methodsToProcess.poll();
            if (processed.contains(callerMethod)) {
                continue;
            }

            SystemDependenceGraph callerCfg = sdgs.get(callerMethod);

            // Traverse its nodes to create the responed sdg nodes and
            // connect them using cg
            Iterator<GraphNode> nIt = callerCfg.getNodes().iterator();
            while (nIt.hasNext()) {
                SdgNode node = (SdgNode) nIt.next();

                // Traverse intra-cfg nodes to create the responed sdg nodes
                // result.addNode(SdgNode.createSdgNode(node));

                
                /**
                 * deal with the call-type pointcut match
                 */
                if (node.getCfgNode().getKind() == CfgNodeKind.CALL
                        && !(node.getCfgNode().getASTNode() instanceof Proceed)) {
                    SdgCallSite callNode = (SdgCallSite) node;
                    connectSdgCallSite(cg, cfgs, sdgs, callerMethod, callNode,
                            result, methodsToProcess);
                    // connect the advice and callsite, add the ADVICE_EDGE
                    weaveSdgCallSite(cg, cfgs, sdgs, callerMethod, callNode,
                            result, methodsToProcess);
                }
                
                /**
                 * deal with the execution-type pointcut match
                 */
                if (node.getCfgNode().getKind() == CfgNodeKind.METHOD_ENTRY
                        && !(node.getCfgNode().getASTNode() instanceof Proceed)) {
                    SdgEntry entryNodeTemp = (SdgEntry) node;
                    // connect the advice and method entry, add the ADVICE_EDGE
                    weaveSdgMethod(cg, cfgs, sdgs, callerMethod, entryNodeTemp,
                            result, methodsToProcess);
                }
            }
        }

        return result;
    }

    private void weaveSdgCallSite(CallGraph cg,
            Map<AbstractMethodDeclaration, ControlFlowGraph> cfgs,
            Map<AbstractMethodDeclaration, SystemDependenceGraph> sdgs,
            AbstractMethodDeclaration callerMethod, SdgCallSite callNode,
            SystemDependenceGraph result,
            Queue<AbstractMethodDeclaration> methodsToProcess) {
        ControlFlowGraph callerCfg = cfgs.get(callerMethod);

        // Find all advices that advises this call site.
        InvocationSite is = (InvocationSite) callNode.getCfgNode().getASTNode();
        List<CgAdvice> advices = null;
        try {
            advices = cg.findAdvices(is);
            Map<CfgCall, ControlFlowGraph> callSitesMap = new HashMap<CfgCall, ControlFlowGraph>();

            callSitesMap.put((CfgCall) callNode.getCfgNode(), callerCfg);
            Iterator<CgAdvice> i = advices.iterator();
            while (i.hasNext()) {
                CgAdvice a = i.next();
                AdviceDeclaration advice = (AdviceDeclaration) a.getASTNode();
                SystemDependenceGraph adviceSdg = sdgs.get(advice);
                result.add(adviceSdg);
                result.addEdge(SdgEdgeKind.ADVICE_EDGE, callNode, adviceSdg
                        .getSdgEntry());
//                methodsToProcess.add(advice);
            }
        } catch (CallSiteNotFoundException e) {
            // Skip quitely.
        }
    }
    
    private void weaveSdgMethod(CallGraph cg,
            Map<AbstractMethodDeclaration, ControlFlowGraph> cfgs,
            Map<AbstractMethodDeclaration, SystemDependenceGraph> sdgs,
            AbstractMethodDeclaration callerMethod, SdgEntry MethodEntrylNode,
            SystemDependenceGraph result,
            Queue<AbstractMethodDeclaration> methodsToProcess) {
        ControlFlowGraph callerCfg = cfgs.get(callerMethod);

        // Find all advices that advises this call site.        
        List<CgAdvice> advices = null;
        try {
            advices = cg.findAdvices(callerMethod);
            Map<CfgCall, ControlFlowGraph> callSitesMap = new HashMap<CfgCall, ControlFlowGraph>();

//            callSitesMap.put((CfgCall) MethodEntrylNode.getCfgNode(), callerCfg);
            Iterator<CgAdvice> i = advices.iterator();
            while (i.hasNext()) {
                CgAdvice a = i.next();
                AdviceDeclaration advice = (AdviceDeclaration) a.getASTNode();
                SystemDependenceGraph adviceSdg = sdgs.get(advice);
                result.add(adviceSdg);
                result.addEdge(SdgEdgeKind.ADVICE_EDGE, sdgs.get(callerMethod).getSdgEntry(), adviceSdg
                        .getSdgEntry());
//                methodsToProcess.add(advice);
            }
        } catch (MethodNotFoundException e) {
            // Skip quitely.
        }
    }

    private void connectSdgCallSite(CallGraph cg,
            Map<AbstractMethodDeclaration, ControlFlowGraph> cfgs,
            Map<AbstractMethodDeclaration, SystemDependenceGraph> sdgs,
            AbstractMethodDeclaration callerMethod, SdgCallSite callNode,
            SystemDependenceGraph result,
            Queue<AbstractMethodDeclaration> methodsToProcess) {
        Set<AbstractMethodDeclaration> callees = getCallees(cg, callerMethod,
                callNode.getCfgNode().getASTNode());
        Iterator<AbstractMethodDeclaration> calleeIt = callees.iterator();

        while (calleeIt.hasNext()) {
            AbstractMethodDeclaration method = calleeIt.next();
            ControlFlowGraph toAddCfg = cfgs.get(method);
            SystemDependenceGraph toAdd = sdgs.get(method);
            if (toAdd != null) {
                result.add(toAdd);
                result.addEdge(SdgEdgeKind.CALL_EDGE, callNode, toAdd
                        .getSdgEntry());
                //add parameter_transit
                LinkedList<SdgNode> ll = callNode.getParasNodes();
                LinkedList<SdgNode> jj = ((SdgEntry)toAdd.getSdgEntry()).getParasNodes();
                for(int k=0; k<ll.size(); k++)
                result.addEdge(SdgEdgeKind.PARAMETER_TRANSIT, ll.get(k),jj.get(k));
                
//                methodsToProcess.add(method);
            }
        }
    }

    public Set<AbstractMethodDeclaration> getCallees(CallGraph cg,
            AbstractMethodDeclaration caller, ASTNode methodCall) {
        MethodBinding b = null;

        if (methodCall instanceof MessageSend) {
            MessageSend messageSend = (MessageSend) methodCall;
            b = messageSend.binding;
        } else if (methodCall instanceof ExplicitConstructorCall) {
            ExplicitConstructorCall explicitConstructor = (ExplicitConstructorCall) methodCall;
            b = explicitConstructor.binding;
        } else if (methodCall instanceof AllocationExpression) {
            AllocationExpression allocationExpression = (AllocationExpression) methodCall;
            b = allocationExpression.binding;
        }

        Set<AbstractMethodDeclaration> result = new HashSet<AbstractMethodDeclaration>();
        if (b != null) {
            AbstractMethodDeclaration callee = b.sourceMethod();
            List<CgMethod> callees = null;
            try {
                callees = cg.findOverridingCallees(caller, callee);
            } catch (MethodNotFoundException e) {
                System.err.println("Error occurred while finding callees!");
                e.printStackTrace();
            }
            if (callees != null) {
                Iterator<CgMethod> i = callees.iterator();
                while (i.hasNext()) {
                    CgMethod node = i.next();
                    if (node.getASTNode() != null) {
                        result.add((AbstractMethodDeclaration) node
                                .getASTNode());
                    }
                }
            }
        }

        return result;
    }

    public Map<AbstractMethodDeclaration, SystemDependenceGraph> getResults() {
        return Sdgs;
    };
}
